﻿using System;
using NHibernate;
using NCommet.Core.Agents;
using log4net;
using NCommet.Core;
using System.Collections.Generic;
using System.Reflection;
using System.Collections;

namespace NCommet.Modules.Dao
{

    /// <summary>
    /// It implements <see cref="NCommet.Core.Agents.IDaoEventsSink"/>. Additionally, it implements <see cref="NHibernate.IInterceptor"/> so that it can subscribe to NHibernate's events. Then, it passes the events pertaining to the deletion and saving of <see cref="NCommet.Core.Item"/> objects to every <see cref="NCommet.Core.Agents.IDaoEventsListener"/> that has been added to it.
    /// </summary>
    /// <remarks>
    ///     <para>
    ///     When a <see cref="NCommet.Modules.Dao.DaoInterceptor"/> is instantiated, it automatically adds itself to the <see cref="NCommet.Modules.Dao.CompositeInterceptor"/> of <see cref="NCommet.Modules.Dao.NHibernateSessionManager"/>.
    ///     </para>
    ///     <para>
    ///     When used as an implementation of <see cref="NCommet.Core.Agents.IDaoEventsSink"/>, <see cref="NCommet.Core.Services.EventSink"/> automatically subscribes to the events of this class. As such, NHibernate's events are transparently passed to the <see cref="NCommet.Core.Services.EventSink"/>. The user needs only subscribe to <see cref="NCommet.Core.Services.EventSink"/> for events originating both from NCommet and NHibernate.
    ///     </para>
    /// </remarks>
    public class DaoInterceptor : IDaoEventsSink, IInterceptor 
    {
        private static readonly ILog log = LogManager.GetLogger("DaoInterceptor");

        public DaoInterceptor()
        {
            NHibernateSessionManager.Instance.AddInterceptor(this);
        }

        private bool enabled = true;

        public bool Enabled
        {
            get { return enabled; }
            set { enabled = value; }
        }

        private bool insideDelete;

        internal bool InsideDelete
        {
            get { return insideDelete; }
            set { insideDelete = value; }
        }
	
        #region IInterceptor Members

        public int[] FindDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types)
        {
            return null;
        }

        public object Instantiate(Type type, object id)
        {
            return null;
        }

        public object IsUnsaved(object entity)
        {
            return null;
        }

        public void OnDelete(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
        {
            if (Enabled)
            {
                log.DebugFormat("OnDelete id={0} type={1}", id, entity.GetType());
                if (entity is Item)
                    NotifyItemDeleted((Item)entity);
            }
        }

        private ArrayList lstSaving = new ArrayList();

        /// <summary>
        /// OnFlushDirty is called only for non-transient objects.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="id"></param>
        /// <param name="currentState"></param>
        /// <param name="previousState"></param>
        /// <param name="propertyNames"></param>
        /// <param name="types"></param>
        /// <returns></returns>
        public bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types)
        {
            if (Enabled && !InsideDelete)
            {
                if (!lstSaving.Contains(entity))
                    lstSaving.Add(entity);

                log.DebugFormat("OnFlushDirty id={0} type={1}", id, entity.GetType());
                if (entity is Item)
                {
                    NotifyItemSaving((Item)entity);
                }
                else if (entity is ContentBase)
                {
                    for (int i = 0; i < propertyNames.Length; i++)
                    {
                        NotifyItemSaving(((ContentBase)entity).Container);

                        if (propertyNames[i] == "SerializedData")
                        {
                            log.DebugFormat("OnFlushDirty property={0}, curr={1}", propertyNames[i], currentState[i]);

                            PropertyInfo pi = entity.GetType().GetProperty("SerializedData", BindingFlags.NonPublic | BindingFlags.Instance);
                            if (pi != null)
                            {
                                log.DebugFormat("OnFlushDirty ela to xoyme");
                                string inObject = pi.GetValue(entity, null) as string;

                                string inArray = currentState[i] as string;

                                if (inObject != inArray)
                                {
                                    log.DebugFormat("OnFlushDirty will return true");
                                    currentState[i] = inObject;
                                    return true;
                                }
                            }

                            break;
                        }
                    }
                }
            }
            return false;
        }

        public bool OnLoad(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
        {
            return false;
        }

        /// <summary>
        /// OnSave gets called only for transient objects just before they get saved.
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="id"></param>
        /// <param name="state"></param>
        /// <param name="propertyNames"></param>
        /// <param name="types"></param>
        /// <returns></returns>
        public bool OnSave(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
        {
            if (Enabled)
            {
                if (!lstSaving.Contains(entity))
                    lstSaving.Add(entity);
                log.DebugFormat("OnSave id={0} type={1}", id, entity.GetType());
                if (entity is Item)
                    NotifyItemSaving((Item)entity);
            }
            return false;
        }

        private object GetId(object o)
        {
            PropertyInfo pi = o.GetType().GetProperty("Id");
            if (pi != null)
                return pi.GetValue(o, null);
            else
                return null;
        }

        public void PostFlush(System.Collections.ICollection entities)
        {
            if (!Enabled || InsideDelete)
                return;
            log.DebugFormat("PostFlush count={0}", entities.Count);
            foreach (object o in entities)
            {
                if (lstSaving.Contains(o))
                {
                    lstSaving.Remove(o);

                    log.DebugFormat("PostFlush type={0} id={1}", o.GetType(), GetId(o));

                    // EventSink's ItemSaved shouldn't be triggered twice each time an Item is saved.
                    if (o is Item)
                    {
                        NotifyItemSaved((Item)o);
                        lstSaving.Remove(((Item)o).Content);
                    }
                    else if (o is ContentBase)
                    {
                        NotifyItemSaved(((ContentBase)o).Container);
                        lstSaving.Remove(((ContentBase)o).Container);
                    }
                }
            }
            lstSaving.Clear();
        }

        public void PreFlush(System.Collections.ICollection entities)
        {
            
        }

        public void AfterTransactionBegin(ITransaction tx)
        {

        }

        public void AfterTransactionCompletion(ITransaction tx)
        {

        }

        public void BeforeTransactionCompletion(ITransaction tx)
        {

        }

        public void SetSession(ISession session)
        {

        }
        #endregion

        private IList<IDaoEventsListener> listeners = new List<IDaoEventsListener>();

        #region IDaoEventsSink Members


        private void NotifyItemSaved(Item item)
        {
            foreach (IDaoEventsListener listener in listeners)
                listener.ItemSaved(item);
        }

        private void NotifyItemDeleted(Item item)
        {
            foreach (IDaoEventsListener listener in listeners)
                listener.ItemDeleted(item);
        }

        private void NotifyItemSaving(Item item)
        {
            foreach (IDaoEventsListener listener in listeners)
                listener.ItemSaving(item);
        }

        #endregion

        #region IDaoEventsSink Members

        void IDaoEventsSink.AddDaoEventsListener(IDaoEventsListener listener)
        {
            listeners.Add(listener);
        }

        void IDaoEventsSink.RemoveDaoEventsListener(IDaoEventsListener listener)
        {
            listeners.Remove(listener);
        }

        public void AddDaoEventsListener(IDaoEventsListener listener)
        {
            listeners.Add(listener);
        }

        public void RemoveDaoEventsListener(IDaoEventsListener listener)
        {
            listeners.Remove(listener);
        }

        bool IDaoEventsSink.Enabled
        {
            get
            {
                return true;
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        #endregion
    }
}
